package com.ywu.standard.gateway.handler.permission;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.ywu.application.spi.dto.ApiRoleAuthDto;
import com.ywu.application.spi.dto.ApiRouterDto;
import com.ywu.application.spi.entity.ApiRouterEntity;
import com.ywu.application.spi.spi.YwuApplicationSpi;
import com.ywu.boot.context.YwuContext;
import com.ywu.boot.context.YwuContextHolder;
import com.ywu.boot.context.YwuContextRole;
import com.ywu.common.constant.CommonConstant;
import com.ywu.common.gson.GsonUtils;
import com.ywu.common.result.CommonResult;
import com.ywu.standard.gateway.handler.permission.impl.ApiRouterHandlerInterface;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @ClassName ApiRouterPermissionHandler
 * @Description TODO
 * @Author GroundDemo
 * @Date 2024/5/26 13:01
 * @Version 1.0
 **/
@Component
@Slf4j
@Order(1)
public class ApiRouterPermissionHandler implements ApiRouterHandlerInterface {

    @Autowired
    private YwuApplicationSpi ywuApplicationSpi;


    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String YWU_GATEWAY_PERMISSION_API_INFO_REDIS_KEY = "YWU_GATEWAY_PERMISSION_INFO:gateway-api:";

    private static final String YWU_GATEWAY_PERMISSION_ROLE_INFO_REDIS_KEY = "YWU_GATEWAY_PERMISSION_INFO:gateway-role:";

    @Override
    public Object handle(ServerWebExchange exchange) {
        log.debug("ApiRouterPermissionHandler begin");
        if (YwuContextHolder.getCurrentContext().isFilterChain()) {
            return exchange;
        }
        // 序列化json对象
        List<YwuContextRole> userRoleInfo = YwuContextHolder.getCurrentContext().getYwuContestRole();
        // 查询当前API的授权
        ApiRoleAuthDto apiRoleAuthDto = new ApiRoleAuthDto();
        // 当前访问的url编码
        String url = exchange.getRequest().getPath().toString();
        if (url.startsWith("/")) {
            url = url.substring(1);
        }
        // 1、根据API的code查询当前API的信息
        ApiRouterEntity routeData = queryRouteInfo(url);
        if (Objects.isNull(routeData)) {
            log.error("ApiRouterPermissionHandler apiRouteInfoQuery routeData is null");
            return CommonResult.error().setCode(CommonConstant.API_PERMISSION_ERROR)
                    .setErrorCode(CommonConstant.API_PERMISSION_MESSAGE).setMessage("接口鉴权失败");
        }
        Integer id = Integer.parseInt(routeData.getId());
        apiRoleAuthDto.setApiId(id);
        // 2、查询API的授权信息
        List<String> apiRoleCodes = queryApiRoleCodes(url, apiRoleAuthDto);
        if (Objects.isNull(apiRoleCodes)) {
            log.error("ApiRouterPermissionHandler apiRoleInfoQuery apiRoleCodes is null");
            return CommonResult.error().setCode(CommonConstant.API_PERMISSION_ERROR)
                    .setErrorCode(CommonConstant.API_PERMISSION_MESSAGE).setMessage("接口鉴权失败");
        }
        List<String> userRoleCodes = userRoleInfo.stream().map(YwuContextRole::getRoleCode).collect(Collectors.toList());
        log.debug("userRoleCodes is {}", GsonUtils.serialize(userRoleCodes));
        // 当前API角色和当前用户角色取交集
        userRoleCodes.retainAll(apiRoleCodes);
        if (CollectionUtils.isEmpty(userRoleCodes)) {
            log.error("ApiRouterPermissionHandler user role permission is error");
            return CommonResult.error().setCode(CommonConstant.API_PERMISSION_ERROR)
                    .setErrorCode(CommonConstant.API_PERMISSION_MESSAGE).setMessage("接口鉴权失败");
        }
        return exchange;
    }

    /**
    * @Author GroundDemo
    * @Description 查询API授权信息
    * @Date 14:44 2025/3/22
    * @Param [url, apiRoleAuthDto]
    * @return java.util.List<java.lang.String>
    **/
    private List<String> queryApiRoleCodes(String url, ApiRoleAuthDto apiRoleAuthDto) {
        List<String> apiRoleCodes = null;
        // 优先使用缓存
        String redisInfo = redisTemplate.opsForValue().get(YWU_GATEWAY_PERMISSION_ROLE_INFO_REDIS_KEY + url);
        if (Objects.nonNull(redisInfo)) {
            log.debug("gateway query api role info using redis cache");
            apiRoleCodes = GsonUtils.deserializeList(redisInfo, String.class);
        } else {
            CompletableFuture<CommonResult<Object>> commonResultCompletableFuture =
                    CompletableFuture.supplyAsync(() -> ywuApplicationSpi.queryApiRoleInfo(apiRoleAuthDto));
            CommonResult<Object> objectCommonResult = null;
            try {
                objectCommonResult = commonResultCompletableFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                log.error("handleApiPermission commonResultCompletableFuture queryApiRoleInfo error {}", e.getMessage());
                return null;
            }
            if (!StringUtils.equals("200", objectCommonResult.getCode())) {
                log.error("ApiRouterPermissionHandler queryApiRoleInfo res error");
                return null;
            }
            Object data = objectCommonResult.getData();
            String serialize = GsonUtils.serialize(data);
            List<YwuContextRole> apiRoles = GsonUtils.deserializeList(serialize, YwuContextRole.class);
            if (CollectionUtils.isEmpty(apiRoles)) {
                log.error("ApiRouterPermissionHandler apiRoles is empty");
                return null;
            }
            apiRoleCodes = apiRoles.stream().map(YwuContextRole::getRoleCode).collect(Collectors.toList());
            // 添加缓存
            redisTemplate.opsForValue().set(YWU_GATEWAY_PERMISSION_ROLE_INFO_REDIS_KEY + url, GsonUtils.serialize(apiRoleCodes));
            redisTemplate.expire(YWU_GATEWAY_PERMISSION_ROLE_INFO_REDIS_KEY + url, 1, TimeUnit.HOURS);
        }
        return apiRoleCodes;
    }

    /**
    * @Author GroundDemo
    * @Description 查询API信息
    * @Date 14:39 2025/3/22
    * @Param [url]
    * @return com.ywu.api.spi.entity.ApiRouterEntity
    **/
    private ApiRouterEntity queryRouteInfo(String url) {
        ApiRouterEntity data = null;
        YwuContext currentContext = YwuContextHolder.getCurrentContext();
        // 优先查询缓存
        String redisInfo = redisTemplate.opsForValue().get(YWU_GATEWAY_PERMISSION_API_INFO_REDIS_KEY + url);
        if (Objects.nonNull(redisInfo)) {
            log.debug("gateway query api info using redis cache");
            data = GsonUtils.deserialize(redisInfo, ApiRouterEntity.class);
        } else {
            ApiRouterDto apiRouterDto = new ApiRouterDto();
            apiRouterDto.setCode(url);
            String tenantId = currentContext.getYwuContextTenant().getTenantId();
            apiRouterDto.setTenantId(tenantId);
            CompletableFuture<CommonResult<ApiRouterEntity>> apiCommonResultCompletableFuture =
                    CompletableFuture.supplyAsync(() -> ywuApplicationSpi.apiRouteInfoQueryForSpi(apiRouterDto));
            CommonResult<ApiRouterEntity> apiObjectCommonResult = null;
            try {
                apiObjectCommonResult = apiCommonResultCompletableFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                log.error("ApiRouterPermissionHandler queryRouteInfo res error");
                return null;
            }
            if (!StringUtils.equals("200", apiObjectCommonResult.getCode())) {
                log.error("ApiRouterPermissionHandler queryRouteInfo res error, code is not 200");
                return null;
            }
            data = apiObjectCommonResult.getData();
            // 添加缓存
            redisTemplate.opsForValue().set(YWU_GATEWAY_PERMISSION_API_INFO_REDIS_KEY + url, GsonUtils.serialize(data));
            redisTemplate.expire(YWU_GATEWAY_PERMISSION_API_INFO_REDIS_KEY + url, 1, TimeUnit.HOURS);
        }
        return data;
    }
}
